/*
* windows.c: Windows front end for my puzzle collection.
*/

#include <windows.h>
#include <commctrl.h>

#include <commdlg.h>
#include <aygshell.h>

#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>

#include "puzzles.h"

#include "resource.h"

#define IDM_NEW       0x0010
#define IDM_RESTART   0x0020
#define IDM_UNDO      0x0030
#define IDM_REDO      0x0040
#define IDM_COPY      0x0050
#define IDM_SOLVE     0x0060
#define IDM_QUIT      0x0070
#define IDM_CONFIG    0x0080
#define IDM_DESC      0x0090
#define IDM_SEED      0x00A0
#define IDM_HELPC     0x00B0
#define IDM_GAMEHELP  0x00C0
#define IDM_ABOUT     0x00D0
#define IDM_SAVE      0x00E0
#define IDM_LOAD      0x00F0
#define IDM_PRINT     0x0100
#define IDM_PRESETS   0x0110

#define IDM_KEYEMUL   0x0400




// MARGIN_DU_X, the margin to use on the left hand side of the
// dialog measured in dialog units.
#define MARGIN_DU_X 4
#define MARGIN_DU_Y 4 // same for top margin

// Where currently selected game parameters are stored in the
// registry. PUZZLECOLLECTION_REG_PATH should end with a \.
#define PUZZLECOLLECTION_REG_PATH "Software\\SimonTatham\\PuzzleCollection\\"
#define SETTINGS_REG_KEY_NAME "Settings"

/*
* Wrapper implementations of functions not supplied by the
* PocketPC API.
*/

#define SHGetSubMenu(hWndMB,ID_MENU) (HMENU)SendMessage((hWndMB), SHCMBM_GETSUBMENU, (WPARAM)0, (LPARAM)ID_MENU)

#undef MessageBox

int MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
	TCHAR wText[2048];
	TCHAR wCaption[2048];

	MultiByteToWideChar (CP_ACP, 0, lpText,    -1, wText,    2048);
	MultiByteToWideChar (CP_ACP, 0, lpCaption, -1, wCaption, 2048);

	return MessageBoxW (hWnd, wText, wCaption, uType);
}

BOOL SetDlgItemTextA(HWND hDlg, int nIDDlgItem, LPCSTR lpString)
{
	TCHAR wText[256];

	MultiByteToWideChar (CP_ACP, 0, lpString, -1, wText, 256);
	return SetDlgItemTextW(hDlg, nIDDlgItem, wText);
}

LPCSTR getenv(LPCSTR buf)
{
	return NULL;
}

BOOL GetKeyboardState(PBYTE pb)
{
	return FALSE;
}

static TCHAR wGameName[256];

#ifdef DEBUGGING
static FILE *debug_fp = NULL;
static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
static int debug_got_console = 0;

void dputs(char *buf)
{
	DWORD dw;

	if (!debug_got_console) {
		if (AllocConsole()) {
			debug_got_console = 1;
			debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
		}
	}
	if (!debug_fp) {
		debug_fp = fopen("debug.log", "w");
	}

	if (debug_hdl != INVALID_HANDLE_VALUE) {
		WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
	}
	fputs(buf, debug_fp);
	fflush(debug_fp);
	OutputDebugString(buf);
}

void debug_printf(char *fmt, ...)
{
	char buf[4096];
	va_list ap;

	va_start(ap, fmt);
	_vsnprintf(buf, 4095, fmt, ap);
	dputs(buf);
	va_end(ap);
}
#endif

static void new_game_size(frontend *fe);
static void new_game_type(frontend *fe);

struct font {
	HFONT font;
	int type;
	int size;
};

struct cfg_aux {
	int ctlid;
	int ctlid_sp;
};

struct blitter {
	HBITMAP bitmap;
	frontend *fe;
	int x, y, w, h;
};

enum { CFG_PRINT = CFG_FRONTEND_SPECIFIC };

struct frontend {
	midend *me;
	int isSmartphone; // true if we're running on a smartphone, false if on a pocket pc etc
	int isPocketPC2003; // true if we're running on a pocket pc 2003 (i.e. a device which does not use soft key based menubars)
	HWND hwnd;
	HWND statusbar;
	HWND numpad;  /* window handle for the numeric pad */
	HWND cfgbox;
	HINSTANCE inst;
	HBITMAP bitmap, prevbm;
	RECT bitmapPosition;  /* game bitmap position within game window */
	HDC hdc;
	COLORREF *colours;
	HBRUSH *brushes;
	HPEN *pens;
	HRGN clip;
	UINT timer;
	DWORD timer_last_tickcount;
	int npresets;
	game_params **presets;
	struct font *fonts;
	int nfonts, fontsize;
	config_item *cfg;
	struct cfg_aux *cfgaux;
	int cfg_which, dlg_done;
	int cfgbox_height;
	HFONT cfgfont;
	HBRUSH oldbr;
	HPEN oldpen;
	enum { DRAWING, PRINTING, NOTHING } drawstatus;
	DOCINFO di;
	int printcount, printw, printh, printsolns, printcurr, printcolour;
	float printscale;
	int printoffsetx, printoffsety;
	float printpixelscale;
	int fontstart;
	int linewidth;
	drawing *dr;
	int xmin, ymin;
};

static int isSmartphone()
{
	TCHAR szPlatform[128];
	int isSmartphone = 0;

	if (SystemParametersInfo(SPI_GETPLATFORMTYPE, sizeof(szPlatform), szPlatform, FALSE))
	{
		if (_tcscmp(szPlatform, _T("SmartPhone")) == 0)
			isSmartphone = 1;
	}

	return isSmartphone;
}

static int isPocketPC()
{
	TCHAR szPlatform[128];
	int isPocketPC = 0;

	if (SystemParametersInfo(SPI_GETPLATFORMTYPE, sizeof(szPlatform), szPlatform, FALSE))
	{
		if (_tcscmp(szPlatform, _T("PocketPC")) == 0)
		{
			isPocketPC = 1;
		}
	}

	return isPocketPC;
}

// Returns true if we are running on a Pocket PC 2003 or below
// device. We won't physically run on a pre 2003 device though,
// but on the off chance we decide to support those devices at
// some stage this function will cause the GUI to do the right
// thing on these old platforms.
static int isPocketPC2003()
{
	int isPocketPC2003 = 0;

	if (isPocketPC())
	{
		OSVERSIONINFO osVer = {0};
		if (GetVersionEx(&osVer))
		{
			isPocketPC2003 = (osVer.dwMajorVersion <= 4);
		}
	}

	return isPocketPC2003;
}


void fatal(char *fmt, ...)
{
	char buf[2048];
	va_list ap;

	va_start(ap, fmt);
	vsprintf(buf, fmt, ap);
	va_end(ap);

	MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);

	exit(1);
}

char *geterrstr(void)
{
	LPVOID lpMsgBuf;
	DWORD dw = GetLastError();
	char *ret;

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | 
		FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		dw,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR) &lpMsgBuf,
		0, NULL );

	ret = dupstr(lpMsgBuf);

	LocalFree(lpMsgBuf);

	return ret;
}

void get_random_seed(void **randseed, int *randseedsize)
{
	SYSTEMTIME *st = snew(SYSTEMTIME);

	GetLocalTime(st);

	*randseed = (void *)st;
	*randseedsize = sizeof(SYSTEMTIME);
}



static void win_status_bar(void *handle, char *text)
{
	TCHAR wText[255];
	frontend *fe = (frontend *)handle;

	MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 255);
	SendMessage(fe->statusbar, SB_SETTEXT,
		(WPARAM) 255 | SBT_NOBORDERS,
		(LPARAM) wText);
}

static blitter *win_blitter_new(void *handle, int w, int h)
{
	blitter *bl = snew(blitter);

	memset(bl, 0, sizeof(blitter));
	bl->w = w;
	bl->h = h;
	bl->bitmap = 0;

	return bl;
}

static void win_blitter_free(void *handle, blitter *bl)
{
	if (bl->bitmap) DeleteObject(bl->bitmap);
	sfree(bl);
}

static void blitter_mkbitmap(frontend *fe, blitter *bl)
{
	HDC hdc = GetDC(fe->hwnd);
	bl->bitmap = CreateCompatibleBitmap(hdc, bl->w, bl->h);
	ReleaseDC(fe->hwnd, hdc);
}

static void win_blitter_save(void *handle, blitter *bl, int x, int y)
{
	frontend *fe = (frontend *)handle;
	HDC hdc_win, hdc_blit;
	HBITMAP prev_blit;

	assert(fe->drawstatus == DRAWING);

	if (!bl->bitmap) blitter_mkbitmap(fe, bl);

	bl->x = x; bl->y = y;

	hdc_win = GetDC(fe->hwnd);
	hdc_blit = CreateCompatibleDC(hdc_win);
	if (!hdc_blit) fatal("hdc_blit failed: 0x%x", GetLastError());

	prev_blit = SelectObject(hdc_blit, bl->bitmap);
	if (prev_blit == NULL || prev_blit == (HANDLE)GDI_ERROR)
		fatal("SelectObject for hdc_main failed: 0x%x", GetLastError());

	if (!BitBlt(hdc_blit, 0, 0, bl->w, bl->h,
		fe->hdc, x, y, SRCCOPY))
		fatal("BitBlt failed: 0x%x", GetLastError());

	SelectObject(hdc_blit, prev_blit);
	DeleteDC(hdc_blit);
	ReleaseDC(fe->hwnd, hdc_win);
}

static void win_blitter_load(void *handle, blitter *bl, int x, int y)
{
	frontend *fe = (frontend *)handle;
	HDC hdc_win, hdc_blit;
	HBITMAP prev_blit;

	assert(fe->drawstatus == DRAWING);

	assert(bl->bitmap); /* we should always have saved before loading */

	if (x == BLITTER_FROMSAVED) x = bl->x;
	if (y == BLITTER_FROMSAVED) y = bl->y;

	hdc_win = GetDC(fe->hwnd);
	hdc_blit = CreateCompatibleDC(hdc_win);

	prev_blit = SelectObject(hdc_blit, bl->bitmap);

	BitBlt(fe->hdc, x, y, bl->w, bl->h,
		hdc_blit, 0, 0, SRCCOPY);

	SelectObject(hdc_blit, prev_blit);
	DeleteDC(hdc_blit);
	ReleaseDC(fe->hwnd, hdc_win);
}

void frontend_default_colour(frontend *fe, float *output)
{
	DWORD c = GetSysColor(COLOR_MENU); /* ick */

	output[0] = (float)(GetRValue(c) / 255.0);
	output[1] = (float)(GetGValue(c) / 255.0);
	output[2] = (float)(GetBValue(c) / 255.0);
}

static POINT win_transform_point(frontend *fe, int x, int y)
{
	POINT ret;

	assert(fe->drawstatus != NOTHING);

	//	if (fe->drawstatus == PRINTING) {
	//		ret.x = (int)(fe->printoffsetx + fe->printpixelscale * x);
	//		ret.y = (int)(fe->printoffsety + fe->printpixelscale * y);
	//	} else {
	ret.x = x;
	ret.y = y;
	//	}

	return ret;
}

static void win_text_colour(frontend *fe, int colour)
{
	assert(fe->drawstatus != NOTHING);

	if (fe->drawstatus == PRINTING) {
		int hatch;
		float r, g, b;
		print_get_colour(fe->dr, colour, &hatch, &r, &g, &b);
		//		if (fe->printcolour)
		//			SetTextColor(fe->hdc, RGB(r * 255, g * 255, b * 255));
		//		else
		SetTextColor(fe->hdc,
			hatch == HATCH_CLEAR ? RGB(255,255,255) : RGB(0,0,0));
	} else {
		SetTextColor(fe->hdc, fe->colours[colour]);
	}
}

static void win_set_brush(frontend *fe, int colour)
{
	HBRUSH br;
	assert(fe->drawstatus != NOTHING);

	if (fe->drawstatus == PRINTING) {
		int hatch;
		float r, g, b;
		print_get_colour(fe->dr, colour, &hatch, &r, &g, &b);

		if (fe->printcolour) {
			br = CreateSolidBrush(RGB(r * 255, g * 255, b * 255));
		} else if (hatch == HATCH_SOLID) {
			br = CreateSolidBrush(RGB(0,0,0));
		} else if (hatch == HATCH_CLEAR) {
			br = CreateSolidBrush(RGB(255,255,255));
		} else {
#ifdef _WIN32_WCE
			/*
			* This is only ever required during printing, and the
			* PocketPC port doesn't support printing.
			*/
			fatal("CreateHatchBrush not supported");
#else
			br = CreateHatchBrush(hatch == HATCH_BACKSLASH ? HS_FDIAGONAL :
				hatch == HATCH_SLASH ? HS_BDIAGONAL :
				hatch == HATCH_HORIZ ? HS_HORIZONTAL :
				hatch == HATCH_VERT ? HS_VERTICAL :
				hatch == HATCH_PLUS ? HS_CROSS :
				/* hatch == HATCH_X ? */ HS_DIAGCROSS,
				RGB(0,0,0));
#endif
		}
	} else {
		br = fe->brushes[colour];
	}
	fe->oldbr = SelectObject(fe->hdc, br);
}

static void win_reset_brush(frontend *fe)
{
	HBRUSH br;

	assert(fe->drawstatus != NOTHING);

	br = SelectObject(fe->hdc, fe->oldbr);
	if (fe->drawstatus == PRINTING)
		DeleteObject(br);
}

static void win_set_pen(frontend *fe, int colour, int thin)
{
	HPEN pen;
	assert(fe->drawstatus != NOTHING);

	if (fe->drawstatus == PRINTING) {
		int hatch;
		float r, g, b;
		int width = thin ? 0 : fe->linewidth;

		print_get_colour(fe->dr, colour, &hatch, &r, &g, &b);
		if (fe->printcolour)
			pen = CreatePen(PS_SOLID, width,
			RGB(r * 255, g * 255, b * 255));
		else if (hatch == HATCH_SOLID)
			pen = CreatePen(PS_SOLID, width, RGB(0, 0, 0));
		else if (hatch == HATCH_CLEAR)
			pen = CreatePen(PS_SOLID, width, RGB(255,255,255));
		else {
			assert(!"This shouldn't happen");
			pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
		}
	} else {
		pen = fe->pens[colour];
	}
	fe->oldpen = SelectObject(fe->hdc, pen);
}

static void win_reset_pen(frontend *fe)
{
	HPEN pen;

	assert(fe->drawstatus != NOTHING);

	pen = SelectObject(fe->hdc, fe->oldpen);
	if (fe->drawstatus == PRINTING)
		DeleteObject(pen);
}

static void win_clip(void *handle, int x, int y, int w, int h)
{
	frontend *fe = (frontend *)handle;
	POINT p, q;

	if (fe->drawstatus == NOTHING)
		return;

	p = win_transform_point(fe, x, y);
	q = win_transform_point(fe, x+w, y+h);
	IntersectClipRect(fe->hdc, p.x, p.y, q.x, q.y);
}

static void win_unclip(void *handle)
{
	frontend *fe = (frontend *)handle;

	if (fe->drawstatus == NOTHING)
		return;

	SelectClipRgn(fe->hdc, NULL);
}

static void win_draw_text(void *handle, int x, int y, int fonttype,
						  int fontsize, int align, int colour, char *text)
{
	frontend *fe = (frontend *)handle;
	POINT xy;
	int i;
	LOGFONT lf;

	if (fe->drawstatus == NOTHING)
		return;

	//	if (fe->drawstatus == PRINTING)
	//		fontsize = (int)(fontsize * fe->printpixelscale);

	xy = win_transform_point(fe, x, y);

	/*
	* Find or create the font.
	*/
	for (i = fe->fontstart; i < fe->nfonts; i++)
		if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
			break;

	if (i == fe->nfonts) {
		if (fe->fontsize <= fe->nfonts) {
			fe->fontsize = fe->nfonts + 10;
			fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
		}

		fe->nfonts++;

		fe->fonts[i].type = fonttype;
		fe->fonts[i].size = fontsize;

		memset (&lf, 0, sizeof(LOGFONT));
		lf.lfHeight = -fontsize;
		lf.lfWeight = (fe->drawstatus == PRINTING ? 0 : FW_BOLD);
		lf.lfCharSet = DEFAULT_CHARSET;
		lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
		lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
		lf.lfQuality = DEFAULT_QUALITY;
		lf.lfPitchAndFamily = (fonttype == FONT_FIXED ?
			FIXED_PITCH | FF_DONTCARE :
		VARIABLE_PITCH | FF_SWISS);
		wcscpy(lf.lfFaceName, TEXT("Tahoma"));

		fe->fonts[i].font = CreateFontIndirect(&lf);
	}

	/*
	* Position and draw the text.
	*/
	{
		HFONT oldfont;
		TEXTMETRIC tm;
		SIZE size;
		TCHAR wText[256];

		MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 256);

		oldfont = SelectObject(fe->hdc, fe->fonts[i].font);
		if (GetTextMetrics(fe->hdc, &tm)) {
			if (align & ALIGN_VCENTRE)
				xy.y -= (tm.tmAscent+tm.tmDescent)/2;
			else
				xy.y -= tm.tmAscent;
		}

		if (GetTextExtentPoint32(fe->hdc, wText, wcslen(wText), &size)) {
			if (align & ALIGN_HCENTRE)
				xy.x -= size.cx / 2;
			else if (align & ALIGN_HRIGHT)
				xy.x -= size.cx;
		}
		SetBkMode(fe->hdc, TRANSPARENT);
		win_text_colour(fe, colour);

		ExtTextOut(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL);
		SelectObject(fe->hdc, oldfont);
	}
}

static void win_draw_rect(void *handle, int x, int y, int w, int h, int colour)
{
	frontend *fe = (frontend *)handle;
	POINT p, q;

	if (fe->drawstatus == NOTHING)
		return;

	if (fe->drawstatus == DRAWING && w == 1 && h == 1) {
		/*
		* Rectangle() appears to get uppity if asked to draw a 1x1
		* rectangle, presumably on the grounds that that's beneath
		* its dignity and you ought to be using SetPixel instead.
		* So I will.
		*/
		SetPixel(fe->hdc, x, y, fe->colours[colour]);
	} else {
		win_set_brush(fe, colour);
		win_set_pen(fe, colour, TRUE);
		p = win_transform_point(fe, x, y);
		q = win_transform_point(fe, x+w, y+h);
		Rectangle(fe->hdc, p.x, p.y, q.x, q.y);
		win_reset_brush(fe);
		win_reset_pen(fe);
	}
}

static void win_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
{
	frontend *fe = (frontend *)handle;
	POINT pp[2];

	if (fe->drawstatus == NOTHING)
		return;

	win_set_pen(fe, colour, FALSE);
	pp[0] = win_transform_point(fe, x1, y1);
	pp[1] = win_transform_point(fe, x2, y2);
	Polyline(fe->hdc, pp, 2);
	if (fe->drawstatus == DRAWING)
		SetPixel(fe->hdc, pp[1].x, pp[1].y, fe->colours[colour]);
	win_reset_pen(fe);
}

static void win_draw_circle(void *handle, int cx, int cy, int radius,
							int fillcolour, int outlinecolour)
{
	frontend *fe = (frontend *)handle;
	POINT p, q;

	assert(outlinecolour >= 0);

	if (fe->drawstatus == NOTHING)
		return;

	if (fillcolour >= 0)
		win_set_brush(fe, fillcolour);
	else
		fe->oldbr = SelectObject(fe->hdc, GetStockObject(NULL_BRUSH));

	win_set_pen(fe, outlinecolour, FALSE);
	p = win_transform_point(fe, cx - radius, cy - radius);
	q = win_transform_point(fe, cx + radius, cy + radius);
	Ellipse(fe->hdc, p.x, p.y, q.x+1, q.y+1);
	win_reset_brush(fe);
	win_reset_pen(fe);
}

static void win_draw_polygon(void *handle, int *coords, int npoints,
							 int fillcolour, int outlinecolour)
{
	frontend *fe = (frontend *)handle;
	POINT *pts;
	int i;

	if (fe->drawstatus == NOTHING)
		return;

	pts = snewn(npoints+1, POINT);

	for (i = 0; i <= npoints; i++) {
		int j = (i < npoints ? i : 0);
		pts[i] = win_transform_point(fe, coords[j*2], coords[j*2+1]);
	}

	assert(outlinecolour >= 0);

	if (fillcolour >= 0) {
		win_set_brush(fe, fillcolour);
		win_set_pen(fe, outlinecolour, FALSE);
		Polygon(fe->hdc, pts, npoints);
		win_reset_brush(fe);
		win_reset_pen(fe);
	} else {
		win_set_pen(fe, outlinecolour, FALSE);
		Polyline(fe->hdc, pts, npoints+1);
		win_reset_pen(fe);
	}

	sfree(pts);
}

static void win_start_draw(void *handle)
{
	frontend *fe = (frontend *)handle;
	HDC hdc_win;

	assert(fe->drawstatus == NOTHING);

	hdc_win = GetDC(fe->hwnd);
	fe->hdc = CreateCompatibleDC(hdc_win);
	fe->prevbm = SelectObject(fe->hdc, fe->bitmap);
	ReleaseDC(fe->hwnd, hdc_win);
	fe->clip = NULL;
	fe->drawstatus = DRAWING;
}

static void win_draw_update(void *handle, int x, int y, int w, int h)
{
	frontend *fe = (frontend *)handle;
	RECT r;

	if (fe->drawstatus != DRAWING)
		return;

	r.left = x;
	r.top = y;
	r.right = x + w;
	r.bottom = y + h;

	OffsetRect(&r, fe->bitmapPosition.left, fe->bitmapPosition.top);
	InvalidateRect(fe->hwnd, &r, FALSE);
}

static void win_end_draw(void *handle)
{
	frontend *fe = (frontend *)handle;
	assert(fe->drawstatus == DRAWING);
	SelectObject(fe->hdc, fe->prevbm);
	DeleteDC(fe->hdc);
	if (fe->clip) {
		DeleteObject(fe->clip);
		fe->clip = NULL;
	}
	fe->drawstatus = NOTHING;
}

static void win_line_width(void *handle, float width)
{
	frontend *fe = (frontend *)handle;

	assert(fe->drawstatus != DRAWING);
	if (fe->drawstatus == NOTHING)
		return;

	fe->linewidth = (int)(width * fe->printpixelscale);
}

static void win_begin_doc(void *handle, int pages)
{
	frontend *fe = (frontend *)handle;

	assert(fe->drawstatus != DRAWING);
	if (fe->drawstatus == NOTHING)
		return;

	if (StartDoc(fe->hdc, &fe->di) <= 0) {
		char *e = geterrstr();
		MessageBox(fe->hwnd, e, "Error starting to print",
			MB_ICONERROR | MB_OK);
		sfree(e);
		fe->drawstatus = NOTHING;
	}

	/*
	* Push a marker on the font stack so that we won't use the
	* same fonts for printing and drawing. (This is because
	* drawing seems to look generally better in bold, but printing
	* is better not in bold.)
	*/
	fe->fontstart = fe->nfonts;
}

static void win_begin_page(void *handle, int number)
{
	frontend *fe = (frontend *)handle;

	assert(fe->drawstatus != DRAWING);
	if (fe->drawstatus == NOTHING)
		return;

	if (StartPage(fe->hdc) <= 0) {
		char *e = geterrstr();
		MessageBox(fe->hwnd, e, "Error starting a page",
			MB_ICONERROR | MB_OK);
		sfree(e);
		fe->drawstatus = NOTHING;
	}
}

static void win_begin_puzzle(void *handle, float xm, float xc,
							 float ym, float yc, int pw, int ph, float wmm)
{
	frontend *fe = (frontend *)handle;
	int ppw, pph, pox, poy;
	float mmpw, mmph, mmox, mmoy;
	float scale;

	assert(fe->drawstatus != DRAWING);
	if (fe->drawstatus == NOTHING)
		return;

	ppw = GetDeviceCaps(fe->hdc, HORZRES);
	pph = GetDeviceCaps(fe->hdc, VERTRES);
	mmpw = (float)GetDeviceCaps(fe->hdc, HORZSIZE);
	mmph = (float)GetDeviceCaps(fe->hdc, VERTSIZE);

	/*
	* Compute the puzzle's position on the logical page.
	*/
	mmox = xm * mmpw + xc;
	mmoy = ym * mmph + yc;

	/*
	* Work out what that comes to in pixels.
	*/
	pox = (int)(mmox * (float)ppw / mmpw);
	poy = (int)(mmoy * (float)ppw / mmpw);

	/*
	* And determine the scale.
	* 
	* I need a scale such that the maximum puzzle-coordinate
	* extent of the rectangle (pw * scale) is equal to the pixel
	* equivalent of the puzzle's millimetre width (wmm * ppw /
	* mmpw).
	*/
	scale = (wmm * ppw) / (mmpw * pw);

	/*
	* Now store pox, poy and scale for use in the main drawing
	* functions.
	*/
	fe->printoffsetx = pox;
	fe->printoffsety = poy;
	fe->printpixelscale = scale;

	fe->linewidth = 1;
}

static void win_end_puzzle(void *handle)
{
	/* Nothing needs to be done here. */
}

static void win_end_page(void *handle, int number)
{
	frontend *fe = (frontend *)handle;

	assert(fe->drawstatus != DRAWING);

	if (fe->drawstatus == NOTHING)
		return;

	if (EndPage(fe->hdc) <= 0) {
		char *e = geterrstr();
		MessageBox(fe->hwnd, e, "Error finishing a page",
			MB_ICONERROR | MB_OK);
		sfree(e);
		fe->drawstatus = NOTHING;
	}
}

static void win_end_doc(void *handle)
{
	frontend *fe = (frontend *)handle;

	assert(fe->drawstatus != DRAWING);

	/*
	* Free all the fonts created since we began printing.
	*/
	while (fe->nfonts > fe->fontstart) {
		fe->nfonts--;
		DeleteObject(fe->fonts[fe->nfonts].font);
	}
	fe->fontstart = 0;

	/*
	* The MSDN web site sample code doesn't bother to call EndDoc
	* if an error occurs half way through printing. I expect doing
	* so would cause the erroneous document to actually be
	* printed, or something equally undesirable.
	*/
	if (fe->drawstatus == NOTHING)
		return;

	if (EndDoc(fe->hdc) <= 0) {
		char *e = geterrstr();
		MessageBox(fe->hwnd, e, "Error finishing printing",
			MB_ICONERROR | MB_OK);
		sfree(e);
		fe->drawstatus = NOTHING;
	}
}

const struct drawing_api win_drawing = {
	win_draw_text,
	win_draw_rect,
	win_draw_line,
	win_draw_polygon,
	win_draw_circle,
	win_draw_update,
	win_clip,
	win_unclip,
	win_start_draw,
	win_end_draw,
	win_status_bar,
	win_blitter_new,
	win_blitter_free,
	win_blitter_save,
	win_blitter_load,
	win_begin_doc,
	win_begin_page,
	win_begin_puzzle,
	win_end_puzzle,
	win_end_page,
	win_end_doc,
	win_line_width,
};

void print(frontend *fe)
{
	// [CF] - printing has been disabled within this build
	//        if someone is interested in adding printing
	//        support (perhaps via PrintCE etc) most of the
	//        code has been kept, so it should be fairly
	//        easy to re-enable.
}

void deactivate_timer(frontend *fe)
{
	if (!fe)
		return;			       /* for non-interactive midend */
	if (fe->hwnd) KillTimer(fe->hwnd, fe->timer);
	fe->timer = 0;
}

void activate_timer(frontend *fe)
{
	if (!fe)
		return;			       /* for non-interactive midend */
	if (!fe->timer) {
		fe->timer = SetTimer(fe->hwnd, 1, 20, NULL);
		fe->timer_last_tickcount = GetTickCount();
	}
}

static int get_statusbar_height(frontend *fe)
{
	int sy = 0;

	if (fe->statusbar) {
		RECT sr;
		GetWindowRect(fe->statusbar, &sr);
		sy += (sr.bottom - sr.top);
	}

	if (fe->numpad)
	{
		RECT sr;
		GetWindowRect(fe->numpad, &sr);
		sy += (sr.bottom - sr.top);
	}

	return sy;
}

static char * ReadGameParameters()
{
	static char szParameters[800];
	char * pszParameters = NULL;

	HKEY hKey;
	TCHAR szPath[256];
	_tcscpy(szPath, _T(PUZZLECOLLECTION_REG_PATH));
	_tcscat(szPath, wGameName);

	if (RegOpenKeyEx(HKEY_CURRENT_USER, szPath, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		WCHAR szParametersEncoded[800];
		DWORD dwLen, dwType;

		dwLen = sizeof(szParametersEncoded);

		if (RegQueryValueEx(hKey, _T(SETTINGS_REG_KEY_NAME), NULL, &dwType, szParametersEncoded, &dwLen) == ERROR_SUCCESS
			&& dwType == REG_SZ)
		{
			WideCharToMultiByte(CP_ACP, 0, szParametersEncoded, dwLen / sizeof(WCHAR), szParameters, sizeof(szParameters), NULL, NULL);
			pszParameters = szParameters;
		}

		RegCloseKey(hKey);
	}

	return pszParameters;
}

// Places a check beside the settings menu item associated
// with the current game parameters
static void RefreshSettingsMenu(frontend *fe)
{
	// Place a check against the currently selected settings
	HMENU hMenu = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_TYPE);
	int i;
	DWORD dwDisposition;
	HKEY hKey;
	BOOL preset = FALSE;

	// A preset?
	game_params * pCurrentParams = midend_get_params(fe->me);

	for (i = 0; i < fe->npresets; i++)
	{
		if (midend_compare_params(fe->me, fe->presets[i], pCurrentParams))
		{
			CheckMenuItem(hMenu, IDM_PRESETS + 0x10 * i, MF_BYCOMMAND | MF_CHECKED);
			preset = TRUE;
		}
		else
		{
			CheckMenuItem(hMenu, IDM_PRESETS + 0x10 * i, MF_BYCOMMAND);
		}
	}

	thegame.free_params(pCurrentParams);

	// A custom setting?
	if (thegame.can_configure)
	{
		if (!preset)
			CheckMenuItem(hMenu, IDM_CONFIG, MF_BYCOMMAND | MF_CHECKED);
		else
			CheckMenuItem(hMenu, IDM_CONFIG, MF_BYCOMMAND);
	}

	// Store the configuration in the registry
	{
		TCHAR szPath[256];
		_tcscpy(szPath, _T(PUZZLECOLLECTION_REG_PATH));
		_tcscat(szPath, wGameName);

		if (RegCreateKeyEx(HKEY_CURRENT_USER, szPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisposition) == ERROR_SUCCESS)
		{
			static WCHAR szEncodedData[800];
			DWORD dwLen;

			char * pszAsciiData = midend_encode_params(fe->me);
			MultiByteToWideChar (CP_ACP, 0, pszAsciiData, -1, szEncodedData, 255);		
			sfree(pszAsciiData);

			dwLen = (_tcslen(szEncodedData) + 1) * sizeof(TCHAR);
			RegSetValueEx(hKey, _T(SETTINGS_REG_KEY_NAME), 0, REG_SZ, (BYTE *)szEncodedData, dwLen);

			RegCloseKey(hKey);
		}
	}

	// Load the settings into the game
	new_game_type(fe);
}


static void adjust_statusbar(frontend *fe, RECT *r)
{
	int sy;

	int statusbar_height = 0;
	int toolbar_height = 0;

	if (fe->numpad)
	{
		RECT rcBounds;
		GetWindowRect(fe->numpad, &rcBounds);
		toolbar_height = rcBounds.bottom - rcBounds.top;
	}

	if (fe->statusbar)
	{
		RECT rcBounds;
		GetWindowRect(fe->statusbar, &rcBounds);
		statusbar_height = rcBounds.bottom - rcBounds.top;
	}

	sy = get_statusbar_height(fe);

	if (fe->numpad)
	{
		RECT rcBounds;
		GetClientRect(fe->hwnd, &rcBounds);

		SetWindowPos(fe->numpad, NULL, 0, r->bottom-r->top-sy, r->right-r->left,
			sy - statusbar_height, SWP_NOZORDER);

		SendMessage(fe->numpad, TB_SETINDENT, ((rcBounds.right - rcBounds.left) - (10 * 21)) / 2, 0);
	}


	if (fe->statusbar)
	{
		SetWindowPos(fe->statusbar, NULL, 0, r->bottom-r->top-sy+toolbar_height, r->right-r->left,
			sy - toolbar_height, SWP_NOZORDER);
	}
}

/*
* Given a proposed new puzzle size (cx,cy), work out the actual
* puzzle size that would be (px,py) and the window size including
* furniture (wx,wy).
*/

static int check_window_resize(frontend *fe, int cx, int cy,
							   int *px, int *py,
							   int *wx, int *wy, int resize)
{
	RECT r;
	int x, y, sy = get_statusbar_height(fe), changed = 0;

	/* disallow making window thinner than menu bar */
	x = max(cx, fe->xmin);
	y = max(cy - sy, fe->ymin);

	/*
	* See if we actually got the window size we wanted, and adjust
	* the puzzle size if not.
	*/
	midend_size(fe->me, &x, &y, resize);
	if (x != cx || y != cy) {
		/*
		* Resize the window, now we know what size we _really_
		* want it to be.
		*/
		r.left = r.top = 0;
		r.right = x;
		r.bottom = y + sy;
		AdjustWindowRectEx(&r, WS_CAPTION | WS_SYSMENU, TRUE, 0);
		*wx = r.right - r.left;
		*wy = r.bottom - r.top;
		changed = 1;
	}

	*px = x;
	*py = y;

	return changed;
}

/*
* Given the current window size, make sure it's sane for the
* current puzzle and resize if necessary.
*/

static void check_window_size(frontend *fe, int *px, int *py)
{
	RECT r;
	int wx, wy, cx, cy;

	GetClientRect(fe->hwnd, &r);
	cx = r.right - r.left;
	cy = r.bottom - r.top;

	check_window_resize(fe, cx, cy, px, py, &wx, &wy, TRUE /* [CF] - NOTE - this was FALSE*/);

	GetClientRect(fe->hwnd, &r);
	adjust_statusbar(fe, &r);
}


static void get_max_puzzle_size(frontend *fe, int *x, int *y)
{
	// [CF] - TODO - fix this fudge, at present we can get
	// called before the front end is properly initialised.
	// It doesn't really matter what value we return in this
	// situation, as once the front end is fully initialised
	// it will attempt to resize the main window again so
	// we can calculate the proper size..
	if (fe == NULL || fe->hwnd == NULL)
	{
		*x = 240;
		*y = 320;
	}
	else
	{
		RECT r;
		GetClientRect(fe->hwnd, &r);

		*x = r.right - r.left;
		*y = r.bottom - r.top;

		*y -= get_statusbar_height(fe);
	}
}

/* Toolbar buttons on the numeric pad */
static TBBUTTON tbNumpadButtons[] =
{
	{0, IDM_KEYEMUL + '1', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{1, IDM_KEYEMUL + '2', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{2, IDM_KEYEMUL + '3', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{3, IDM_KEYEMUL + '4', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{4, IDM_KEYEMUL + '5', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{5, IDM_KEYEMUL + '6', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{6, IDM_KEYEMUL + '7', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{7, IDM_KEYEMUL + '8', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{8, IDM_KEYEMUL + '9', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
	{9, IDM_KEYEMUL + ' ', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1}
};

static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
{
	frontend *fe;
	int x, y;
	RECT r;

	fe = snew(frontend);

	fe->isSmartphone = isSmartphone();
	fe->isPocketPC2003 = isPocketPC2003();

	fe->me = midend_new(fe, &thegame, &win_drawing, fe);

	if (game_id) {
		*error = midend_game_id(fe->me, game_id);
		if (*error) {
			// [CF] - TODO - produce a better error message, and
			// perhaps not make this so fatal, for example we could
			// just configure with suitable defaults and continue
			// a new game... also we shouldn't prompt if we've got
			// a game ID purely due to us loading the user's preferences
			// out of the registry (if our prefs are screwed, it's better
			// just to silently ignore them...)
			midend_free(fe->me);
			sfree(fe);
			return NULL;
		}
	}

	fe->inst = inst;

	fe->timer = 0;
	fe->hwnd = NULL;

	fe->drawstatus = NOTHING;
	fe->dr = NULL;
	fe->fontstart = 0;

	midend_new_game(fe->me);

	fe->fonts = NULL;
	fe->nfonts = fe->fontsize = 0;

	{
		int i, ncolours;
		float *colours;

		colours = midend_colours(fe->me, &ncolours);

		fe->colours = snewn(ncolours, COLORREF);
		fe->brushes = snewn(ncolours, HBRUSH);
		fe->pens = snewn(ncolours, HPEN);

		for (i = 0; i < ncolours; i++) {
			fe->colours[i] = RGB(255 * colours[i*3+0],
				255 * colours[i*3+1],
				255 * colours[i*3+2]);
			fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
			fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
		}
		sfree(colours);
	}


	// [CF] - TODO - with slight re-ordering we may be able to get rid of this
	// temporary status bar... we end up destorying this instance
	// and creating a new status bar further down in this function
	if (midend_wants_statusbar(fe->me)) {
		fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"),
			WS_CHILD | WS_VISIBLE,
			0, 0, 0, 0, /* status bar does these */
			NULL, NULL, inst, NULL);
	} else
		fe->statusbar = NULL;

	get_max_puzzle_size(fe, &x, &y);
	midend_size(fe->me, &x, &y, TRUE/*[CF] - NOTE - this was FALSE*/);

	r.left = r.top = 0;
	r.right = x;
	r.bottom = y;
	AdjustWindowRectEx(&r, WS_CAPTION | WS_SYSMENU, TRUE, 0);

	fe->hwnd = CreateWindowEx(0, wGameName, wGameName,
		WS_VISIBLE,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, inst, NULL);
	{
		SHMENUBARINFO mbi;
		RECT rc, rcBar, rcTB, rcClient;

		memset (&mbi, 0, sizeof(SHMENUBARINFO));
		mbi.cbSize     = sizeof(SHMENUBARINFO);
		mbi.hwndParent = fe->hwnd;
		mbi.nToolBarId = IDR_MENUBAR1;
		mbi.hInstRes   = inst;

		SHCreateMenuBar(&mbi);

		if (!fe->isSmartphone)
		{
			// When the main window is created using CW_USEDEFAULT the height of the menubar (if one
			// is created is not taken into account). So we resize the window after creating it
			// if a menubar is present
			RECT rc;
			RECT rcMenuBar;

			GetWindowRect(fe->hwnd, &rc);
			GetWindowRect(mbi.hwndMB, &rcMenuBar);
			rc.bottom -= (rcMenuBar.bottom - rcMenuBar.top);

			MoveWindow(fe->hwnd, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, FALSE);
		}

		if ((thegame.flags & REQUIRE_NUMPAD) && !fe->isSmartphone)
		{
			fe->numpad = CreateToolbarEx (fe->hwnd,
				WS_VISIBLE | WS_CHILD | CCS_NOPARENTALIGN | TBSTYLE_FLAT,
				0, 10, inst, IDR_PADTOOLBAR,
				tbNumpadButtons, sizeof (tbNumpadButtons) / sizeof (TBBUTTON),
				0, 0, 14, 15, sizeof (TBBUTTON));
			GetWindowRect(fe->numpad, &rcTB);
			GetClientRect(fe->hwnd, &rcClient);

			MoveWindow(fe->numpad, 
				0, 
				rcClient.bottom - (rcTB.bottom - rcTB.top) - 1,
				rcClient.right,
				rcTB.bottom - rcTB.top,
				FALSE);
			SendMessage(fe->numpad, TB_SETINDENT, (rcClient.right - (10 * 21)) / 2, 0);
		}
		else
			fe->numpad = NULL;
	}

	if (midend_wants_statusbar(fe->me)) {
		RECT sr;
		DestroyWindow(fe->statusbar);
		fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"),
			WS_CHILD | WS_VISIBLE,
			0, 0, 0, 0, /* status bar does these */
			fe->hwnd, NULL, inst, NULL);
		/* Flat status bar looks better on the Pocket PC */
		SendMessage(fe->statusbar, SB_SIMPLE, (WPARAM) TRUE, 0);
		SendMessage(fe->statusbar, SB_SETTEXT,
			(WPARAM) 255 | SBT_NOBORDERS,
			(LPARAM) L"abc");
	} else {
		fe->statusbar = NULL;
	}

	{
		HMENU menu = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_GAME);
		DeleteMenu(menu, 0, MF_BYPOSITION);

		AppendMenu(menu, MF_ENABLED, IDM_NEW, TEXT("&New"));
		AppendMenu(menu, MF_ENABLED, IDM_RESTART, TEXT("&Restart"));

		if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
			thegame.can_configure) {
				int i;

				HMENU sub = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_TYPE);
				DeleteMenu(sub, 0, MF_BYPOSITION);

				fe->presets = snewn(fe->npresets, game_params *);

				for (i = 0; i < fe->npresets; i++) {
					char *name;
					TCHAR wName[255];

					midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);

					/*
					* FIXME: we ought to go through and do something
					* with ampersands here.
					*/

					MultiByteToWideChar (CP_ACP, 0, name, -1, wName, 255);
					AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, wName);
				}
				if (thegame.can_configure) {
					AppendMenu(sub, MF_ENABLED, IDM_CONFIG, TEXT("&Custom..."));
				}

				RefreshSettingsMenu(fe);
		}

		AppendMenu(menu, MF_SEPARATOR, 0, 0);
		//	AppendMenu(menu, MF_ENABLED, IDM_LOAD, TEXT("&Load..."));
		//	AppendMenu(menu, MF_ENABLED, IDM_SAVE, TEXT("&Save..."));
		//	AppendMenu(menu, MF_SEPARATOR, 0, 0);
		//	if (thegame.can_print) {
		//	    AppendMenu(menu, MF_ENABLED, IDM_PRINT, TEXT("&Print..."));
		//	    AppendMenu(menu, MF_SEPARATOR, 0, 0);
		//	}
		AppendMenu(menu, MF_ENABLED, IDM_UNDO, TEXT("Undo"));
		AppendMenu(menu, MF_ENABLED, IDM_REDO, TEXT("Redo"));
		//	if (thegame.can_format_as_text) {
		//	    AppendMenu(menu, MF_SEPARATOR, 0, 0);
		//	    AppendMenu(menu, MF_ENABLED, IDM_COPY, TEXT("&Copy"));
		//	}
		if (thegame.can_solve) {
			AppendMenu(menu, MF_SEPARATOR, 0, 0);
			AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve"));
		}
		AppendMenu(menu, MF_SEPARATOR, 0, 0);
		//	AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit"));
		AppendMenu(menu, MF_ENABLED, IDM_ABOUT, TEXT("&About"));
	}

	fe->bitmap = NULL;
	new_game_size(fe); /* initialises fe->bitmap */
	check_window_size(fe, &x, &y);

	SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);

	ShowWindow(fe->hwnd, SW_SHOWNORMAL);
	SetForegroundWindow(fe->hwnd);

	midend_redraw(fe->me);

	return fe;
}

static HFONT dialog_title_font(HWND hWnd)
{
	static HFONT hf = NULL;
	LOGFONT lf;
	HRESULT hr;

	if (hf)
		return hf;

	// Get the existing dialog font and
	// make a bold variant of it
	memset (&lf, 0, sizeof(LOGFONT));
	hf = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);	
	GetObject(hf, sizeof(lf), &lf);

	lf.lfWeight = FW_BOLD;

	hf = CreateFontIndirect(&lf);
	return hf;
}

static void make_dialog_full_screen(frontend *fe, HWND hwnd, UINT nMenuResourceId)
{
	SHINITDLGINFO shidi;
	SHMENUBARINFO mbi;
	RECT rc, rcBar;

	/* Make dialog full screen */
	memset(&shidi, 0, sizeof(shidi));
	shidi.dwMask = SHIDIM_FLAGS;
	shidi.dwFlags = SHIDIF_SIZEDLGFULLSCREEN;
	if (fe->isPocketPC2003)
		shidi.dwFlags |= SHIDIF_EMPTYMENU | SHIDIF_DONEBUTTON;
	shidi.hDlg = hwnd;
	SHInitDialog(&shidi);

	if (!fe->isPocketPC2003)
	{
		memset (&mbi, 0, sizeof(SHMENUBARINFO));
		mbi.cbSize     = sizeof(SHMENUBARINFO);
		mbi.hwndParent = hwnd;
		mbi.nToolBarId = nMenuResourceId;
		mbi.hInstRes   = GetModuleHandle(NULL);
		SHCreateMenuBar(&mbi);
	}
}

static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
								 WPARAM wParam, LPARAM lParam)
{
	frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);

	switch (msg) {
case WM_INITDIALOG:
	{
		char title[256];

		fe = (frontend *)lParam;
		SetWindowLong(hwnd, GWL_USERDATA, fe);

		make_dialog_full_screen(fe, hwnd, IDR_MENUBAR3);

		sprintf(title, "About %.250s", thegame.name);
		SetDlgItemTextA(hwnd, IDC_ABOUT_CAPTION, title);
		SendDlgItemMessage(hwnd, IDC_ABOUT_CAPTION, WM_SETFONT,
			(WPARAM) dialog_title_font(hwnd), 0);

		{
			char title[512];
			sprintf(title, "%s from Simon Tatham's Portable Puzzle Collection.\r\n\r\nPocket PC port by Darek Olszewski and Christopher Fairbairn.\r\n\r\n%s", thegame.name, ver);
			SetDlgItemTextA(hwnd, IDC_ABOUT_GAME, title);
		}
	}
	return TRUE;

case WM_SIZE:
	{
		HWND hWndControl = GetDlgItem(hwnd, IDC_ABOUT_GAME);
		RECT rcBounds;

		GetWindowRect(hWndControl, &rcBounds);

		{
			POINT pt1 = {rcBounds.left, rcBounds.top};
			POINT pt2 = {rcBounds.right, rcBounds.bottom};

			ScreenToClient(hwnd, &pt1);
			ScreenToClient(hwnd, &pt2);

			rcBounds.left = pt1.x;
			rcBounds.top = pt1.y;
			rcBounds.right =  LOWORD(lParam) - DialogUnitToPixel_X(hwnd, MARGIN_DU_X);
			rcBounds.bottom = HIWORD(lParam) - DialogUnitToPixel_Y(hwnd, MARGIN_DU_Y);
		}

		MoveWindow(hWndControl, rcBounds.left, rcBounds.top, rcBounds.right - rcBounds.left, rcBounds.bottom - rcBounds.top, FALSE);
	}
	return 0;

case WM_COMMAND:
	if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		EndDialog(hwnd, LOWORD(wParam));
	return 0;

case WM_CLOSE:
	EndDialog(hwnd, IDOK);
	return 0;


case WM_PAINT:
	{
		PAINTSTRUCT ps;
		RECT rcBounds;
		HPEN hPen = GetStockObject(BLACK_PEN);
		HPEN hOldPen;

		BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rcBounds);
		hOldPen = (HPEN)SelectObject(ps.hdc, hPen);

		{
			int x = DialogUnitToPixel_X(hwnd, MARGIN_DU_X); 
			int y = DialogUnitToPixel_Y(hwnd, 15);
			MoveToEx(ps.hdc, x, y, NULL);
			LineTo(ps.hdc, (rcBounds.right - rcBounds.left) - x, y);
		}

		SelectObject(ps.hdc, hOldPen);

		EndPaint(hwnd, &ps);
	}
	break;
	}

	return 0;
}

/*
* Wrappers on midend_{get,set}_config, which extend the CFG_*
* enumeration to add CFG_PRINT.
*/
static config_item *frontend_get_config(frontend *fe, int which,
										char **wintitle)
{
	if (which < CFG_FRONTEND_SPECIFIC) {
		return midend_get_config(fe->me, which, wintitle);
	} else if (which == CFG_PRINT) {
		config_item *ret;
		int i;

		*wintitle = snewn(40 + strlen(thegame.name), char);
		sprintf(*wintitle, "%s print setup", thegame.name);

		ret = snewn(8, config_item);

		i = 0;

		ret[i].name = "Number of puzzles to print";
		ret[i].type = C_STRING;
		ret[i].sval = dupstr("1");
		ret[i].ival = 0;
		i++;

		ret[i].name = "Number of puzzles across the page";
		ret[i].type = C_STRING;
		ret[i].sval = dupstr("1");
		ret[i].ival = 0;
		i++;

		ret[i].name = "Number of puzzles down the page";
		ret[i].type = C_STRING;
		ret[i].sval = dupstr("1");
		ret[i].ival = 0;
		i++;

		ret[i].name = "Percentage of standard size";
		ret[i].type = C_STRING;
		ret[i].sval = dupstr("100.0");
		ret[i].ival = 0;
		i++;

		ret[i].name = "Include currently shown puzzle";
		ret[i].type = C_BOOLEAN;
		ret[i].sval = NULL;
		ret[i].ival = TRUE;
		i++;

		ret[i].name = "Print solutions";
		ret[i].type = C_BOOLEAN;
		ret[i].sval = NULL;
		ret[i].ival = FALSE;
		i++;

		if (thegame.can_print_in_colour) {
			ret[i].name = "Print in colour";
			ret[i].type = C_BOOLEAN;
			ret[i].sval = NULL;
			ret[i].ival = FALSE;
			i++;
		}

		ret[i].name = NULL;
		ret[i].type = C_END;
		ret[i].sval = NULL;
		ret[i].ival = 0;
		i++;

		return ret;
	} else {
		assert(!"We should never get here");
		return NULL;
	}
}

static char *frontend_set_config(frontend *fe, int which, config_item *cfg)
{
	if (which < CFG_FRONTEND_SPECIFIC) {
		return midend_set_config(fe->me, which, cfg);
	} else if (which == CFG_PRINT) {
		if ((fe->printcount = atoi(cfg[0].sval)) <= 0)
			return "Number of puzzles to print should be at least one";
		if ((fe->printw = atoi(cfg[1].sval)) <= 0)
			return "Number of puzzles across the page should be at least one";
		if ((fe->printh = atoi(cfg[2].sval)) <= 0)
			return "Number of puzzles down the page should be at least one";
		if ((fe->printscale = (float)atof(cfg[3].sval)) <= 0)
			return "Print size should be positive";
		fe->printcurr = cfg[4].ival;
		fe->printsolns = cfg[5].ival;
		fe->printcolour = thegame.can_print_in_colour && cfg[6].ival;
		return NULL;
	} else {
		assert(!"We should never get here");
		return "Internal error";
	}
}

static int DialogUnitToPixel_X(HWND hWnd, int du)
{
	RECT rc = {0,0,4,8};

	MapDialogRect(hWnd, &rc);

	return (int)(du * rc.right / 4.0f);
}

static int DialogUnitToPixel_Y(HWND hWnd, int du)
{
	RECT rc = {0,0,4,8};

	MapDialogRect(hWnd, &rc);

	return (int)(du * rc.bottom / 8.0f);

}

/* Separate version of mkctrl function for the Pocket PC. */
/* Control coordinates should be specified in dialog units. */
static HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
				   LPCTSTR wclass, int wstyle,
				   int exstyle, const char *wtext, int wid)
{
	HWND hwnd;
	RECT rc;
	TCHAR wwtext[256];

	/* Convert dialog units into pixels */
	rc.left = x1;  rc.right  = x2;
	rc.top  = y1;  rc.bottom = y2;
	MapDialogRect(fe->cfgbox, &rc);

	MultiByteToWideChar (CP_ACP, 0, wtext, -1, wwtext, 256);

	hwnd = CreateWindowEx(exstyle, wclass, wwtext,
		wstyle | WS_CHILD | WS_VISIBLE,
		rc.left, rc.top,
		rc.right - rc.left, rc.bottom - rc.top,
		fe->cfgbox, (HMENU) wid, fe->inst, NULL);

	return hwnd;
}

static void relayout_config_controls(frontend * fe)
{
	RECT rcWidth, rcBounds;
	config_item *i;
	struct cfg_aux *j;
	HWND hWndControl;
	SCROLLINFO si;

	// Don't do anything if we have not add any configuration
	// controls yet
	if (!fe->cfgaux)
		return;

	// Configure the vertical scrollbar to enable the
	// user to scroll into view any configuration option
	// which is outside the screen area
	if (!fe->isSmartphone)
	{
		GetClientRect(fe->cfgbox, &rcWidth);

		memset(&si, 0, sizeof(si));
		si.cbSize = sizeof(si);
		si.fMask = SIF_RANGE;
		si.nMin = 0;
		si.nMax = fe->cfgbox_height - (rcWidth.bottom - rcWidth.top);

		if (si.nMax < 0)
		{
			// If we are going to remove the scrollbar then
			// undo any scrolling we have done
			SCROLLINFO si2;
			memset(&si2, 0, sizeof(si2));
			si2.cbSize = sizeof(si2);
			si2.fMask = SIF_POS;
			GetScrollInfo(fe->cfgbox, SB_VERT, &si2);
			ScrollWindowEx(fe->cfgbox, 0, si2.nPos, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_SCROLLCHILDREN);
		}

		SetScrollInfo(fe->cfgbox, SB_VERT, &si, TRUE);

		// [CF] - TODO - ensure control with focus is scrolled into view
	}

	// Move all the configuration controls to their new size
	// to ensure they occupy the full width of the screen
	GetClientRect(fe->cfgbox, &rcWidth);

	for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++)
	{
		if (j->ctlid != NULL)
		{
			POINT pt;
			int x;
			int spinnerSize = 0;

			hWndControl = GetDlgItem(fe->cfgbox, j->ctlid);
			GetWindowRect(hWndControl, &rcBounds);

			pt.x = rcBounds.left;
			pt.y = rcBounds.top;
			ScreenToClient(fe->cfgbox, &pt);

			if (j->ctlid_sp != NULL)
			{
				HWND hWnd2 = GetDlgItem(fe->cfgbox, j->ctlid_sp);
				RECT rcBounds2;
				GetWindowRect(hWnd2, &rcBounds2);
				spinnerSize = rcBounds2.right - rcBounds2.left;
			}

			x = rcWidth.right - (rcBounds.right - rcBounds.left) - spinnerSize - DialogUnitToPixel_X(fe->cfgbox, MARGIN_DU_X);
			SetWindowPos(hWndControl, NULL, x, pt.y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
		}
	}
}

static void create_config_controls(frontend * fe)
{
	int id, nctrls;
	RECT rcBounds;
	int col1l, col1r, col2l, col2r, y;
	config_item *i;
	struct cfg_aux *j;
	HWND ctl;
	RECT rcClientRect;
	int maxLabelWidth = 0;
	HDC hDC;

	int standardStyles = WS_BORDER;
	if (fe->isSmartphone)
		standardStyles = 0;

	/* Try to determine the maximum width required for the configuration option labels */
	hDC = GetDC(fe->cfgbox);

	for (i = fe->cfg; i->type != C_END; i++)
	{
		TCHAR wText[256];

		MultiByteToWideChar (CP_ACP, 0, i->name, -1, wText, 256);

		DrawText(hDC, wText, -1, &rcBounds, DT_CALCRECT | DT_SINGLELINE | DT_NOPREFIX | DT_TOP | DT_LEFT);

		if (rcBounds.right - rcBounds.left > maxLabelWidth)
			maxLabelWidth = (rcBounds.right - rcBounds.left);
	}

	ReleaseDC(fe->cfgbox, hDC);

	{
		// [CF] - TODO - wrap this up into a helper function similiar to
		// DialogUnitToPixel_X which does the reverse operation...
		RECT rc = {0,0,4,8};
		LONG baseUnitX, baseUnitY;
		MapDialogRect(fe->cfgbox, &rc);

		GetClientRect(fe->cfgbox, &rcClientRect);

		baseUnitX =rc.right;
		baseUnitY = rc.bottom;

#define pixel2dlg_x(pixel_x) ((pixel_x) * 4 / baseUnitX)
#define pixel2dlg_y(pixel_y) ((pixel_y) * 8 / baseUnitY)

		/* Control placement done in dialog units */
		if (fe->isSmartphone)
		{
			/* Label column */
			col1l = MARGIN_DU_X;
			col1r = pixel2dlg_x(rcClientRect.right - rcClientRect.left) - col1l;

			/* Input column (edit boxes and combo boxes) */
			col2l = col1l;
			col2r = pixel2dlg_x(rcClientRect.right - rcClientRect.left) - col1l;
		}
		else
		{
			/* Label column */
			col1l = MARGIN_DU_X;
			col1r = col1l + pixel2dlg_x(maxLabelWidth) + 1;

			/* Input column (edit boxes and combo boxes) */
			col2l = col1r + MARGIN_DU_X;
			col2r = pixel2dlg_x(rcClientRect.right - rcClientRect.left) - col1l;
		}
	}

	/*
	* Count the controls so we can allocate cfgaux.
	*/
	for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
		nctrls++;
	fe->cfgaux = snewn(nctrls, struct cfg_aux);

	id = 1000;

	if (fe->isSmartphone)
	{
		// Hide the configuration dialog caption (we have limited screen
		// realestate so want to maximise the amount used for configuration
		// settings
		ShowWindow(GetDlgItem(fe->cfgbox, IDC_CONFIG_CAPTION), SW_HIDE);
		ShowWindow(GetDlgItem(fe->cfgbox, IDC_CONFIG_LINE), SW_HIDE);
		y = MARGIN_DU_Y;
	}
	else
	{
		y = 22; /* Leave some room for the dialog title */
	}

	for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
		j->ctlid_sp = 0;

		switch (i->type) {
	case C_STRING:
		/*
		* Edit box with a label beside it.
		*/
		mkctrl(fe, col1l, col1r, y + 1, y + 11,
			TEXT("Static"), SS_LEFTNOWORDWRAP, 0, i->name, id++);
		if (fe->isSmartphone)
			y += 15;
		mkctrl(fe, col2l, col2r, y, y + 12,
			TEXT("EDIT"), standardStyles | WS_TABSTOP | ES_AUTOHSCROLL,
			0, "", (j->ctlid = id++));
		if (fe->isSmartphone)
		{
			// [CF] - TODO - we need to be smarter about this... it may be possible
			// for a game to have a C_STRING config option, where it is expecting alpha input???
			// [CF] - TODO - no matter if we're a smartphone or not if we determine this is a numeric
			// field we should provide some input filtering so only numeric digits are accepted, even if
			// the input mode is set to numbers there is nothingstopping the user changing it, or using
			// an external keypad etc..
			HWND hWndEdit = GetDlgItem(fe->cfgbox, j->ctlid);
			SendMessage(hWndEdit, EM_SETINPUTMODE, 0, EIM_NUMBERS);
		}
		SetDlgItemTextA(fe->cfgbox, j->ctlid, i->sval);
		break;

	case C_BOOLEAN:
		/*
		* Simple checkbox.
		*/
		mkctrl(fe, col1l, col2r, y + 1, y + 11, TEXT("BUTTON"),
			BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
			0, i->name, (j->ctlid = id++));
		CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
		break;

	case C_CHOICES:
		/*
		* Drop-down list with a label beside it.
		*/
		mkctrl(fe, col1l, col1r, y + 1, y + 11,
			TEXT("STATIC"), SS_LEFTNOWORDWRAP, 0, i->name, id++);

		if (fe->isSmartphone)
		{
			char szTitle[256];
			sprintf(szTitle, "Select %s", i->name);

			y += 15;

			ctl = mkctrl(fe, col2l, col2r, y, y + 11,
				TEXT("LISTBOX"), standardStyles | WS_TABSTOP | LBS_NOINTEGRALHEIGHT | LBS_NOTIFY, 0, "", (j->ctlid = id++));
			mkctrl(fe, 0, 0, 0, 0, UPDOWN_CLASS, UDS_AUTOBUDDY | UDS_ALIGNRIGHT | UDS_HORZ | UDS_ARROWKEYS | UDS_SETBUDDYINT | UDS_WRAP | UDS_EXPANDABLE, 0, szTitle, (j->ctlid_sp = id++));
		}
		else
		{
			ctl = mkctrl(fe, col2l, col2r, y, y + 48,
				TEXT("COMBOBOX"), standardStyles | WS_TABSTOP |
				CBS_DROPDOWNLIST | CBS_HASSTRINGS,
				0, "", (j->ctlid = id++));
		}

		{
			char c, *p, *q, *str;

			p = i->sval;
			c = *p++;
			while (*p) {
				q = p;
				while (*q && *q != c) q++;
				str = snewn(q-p+1, char);
				strncpy(str, p, q-p);
				str[q-p] = '\0';
				{
					TCHAR ws[50];
					MultiByteToWideChar (CP_ACP, 0, str, -1, ws, 50);
					if (fe->isSmartphone)
						SendMessage(ctl, LB_ADDSTRING, 0, (LPARAM)ws);
					else
						SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)ws);					
				}

				sfree(str);
				if (*q) q++;
				p = q;
			}
		}
		if (fe->isSmartphone)
			SendMessage(ctl, LB_SETCURSEL, i->ival, 0);
		else
			SendMessage(ctl, CB_SETCURSEL, i->ival, 0);		
		break;
		}

		y += 15;
	}

	// [CF] - TODO - since we always want WS_VSCROLL add it to the resources...
	// and get rid of the GetWindowLong/SetWindowLong stuff below...
	if (fe->isSmartphone)
	{
		// Add a scrollbar so that we can scroll to
		// all the configuration settings if they
		// don't fit on screen at once
		HWND hWndMenuBar;
		LONG style = GetWindowLong(fe->cfgbox, GWL_STYLE);
		style |= WS_VSCROLL;
		SetWindowLong(fe->cfgbox, GWL_STYLE, style);

		// Since our dialog has an edit control we need to override
		// the handling of the back key so it acts as a delete...
		hWndMenuBar = SHFindMenuBar(fe->cfgbox);
		SendMessage(hWndMenuBar, SHCMBM_OVERRIDEKEY, VK_TBACK, 
			MAKELPARAM(SHMBOF_NODEFAULT | SHMBOF_NOTIFY, 
			SHMBOF_NODEFAULT | SHMBOF_NOTIFY));
	}
	else
	{
		LONG style = GetWindowLong(fe->cfgbox, GWL_STYLE);
		style |= WS_VSCROLL;
		SetWindowLong(fe->cfgbox, GWL_STYLE, style);
	}

	fe->cfgbox_height =  DialogUnitToPixel_Y(fe->cfgbox, y);

	// Add context sensitive (tap and hold) menus to edit
	// controls etc on pocket pc to allow cut/copy/paste
	CreateWindow(WC_SIPPREF, L"" , WS_CHILD, 0, 0, 0, 0, fe->cfgbox, 0, fe->inst, 0);

	// [CF] - TODO - select the first control i.e. give it focus...
}

static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
								  WPARAM wParam, LPARAM lParam)
{
	frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
	config_item *i;
	struct cfg_aux *j;

	// Ensure the SIP is always displayed within this dialog
	// and that it remembers it's state when the user
	// navigates away from the dialog and returns
	SHInputDialog(hwnd, msg, wParam, lParam);

	switch (msg) {
case WM_INITDIALOG:
	{
		char *title;

		fe = (frontend *) lParam;
		SetWindowLong(hwnd, GWL_USERDATA, lParam);
		fe->cfgbox = hwnd;

		fe->cfg = frontend_get_config(fe, fe->cfg_which, &title);
		fe->cfgaux = NULL;

		make_dialog_full_screen(fe, hwnd, IDR_MENUBAR2);

		SetDlgItemTextA(hwnd, IDC_CONFIG_CAPTION, title);
		SendDlgItemMessage(hwnd, IDC_CONFIG_CAPTION, WM_SETFONT,
			(WPARAM) dialog_title_font(hwnd), 0);

		if (fe->isSmartphone)
		{
			// Smartphone hides the IDC_CONFIG_CAPTION label above
			// and relies upon the navbar caption displaying the
			// config caption
			TCHAR ws[50];
			MultiByteToWideChar (CP_ACP, 0, title, -1, ws, 50);
			SetWindowText(hwnd, ws);
		}

		create_config_controls(fe);
	}
	return TRUE;

case WM_SIZE:
	relayout_config_controls(fe);
	break;

case WM_PAINT:
	{
		PAINTSTRUCT ps;
		BeginPaint(hwnd, &ps);

		if (fe && !fe->isSmartphone)
		{
			RECT rcBounds;
			HPEN hPen = GetStockObject(BLACK_PEN);
			HPEN hOldPen;

			GetClientRect(hwnd, &rcBounds);
			hOldPen = (HPEN)SelectObject(ps.hdc, hPen);

			{
				SCROLLINFO si2;
				int x = DialogUnitToPixel_X(hwnd, MARGIN_DU_X); 
				int y = DialogUnitToPixel_Y(hwnd, 15);

				memset(&si2, 0, sizeof(si2));
				si2.cbSize = sizeof(si2);
				si2.fMask = SIF_POS;
				GetScrollInfo(fe->cfgbox, SB_VERT, &si2);

				y -= si2.nPos;

				MoveToEx(ps.hdc, x, y, NULL);
				LineTo(ps.hdc, (rcBounds.right - rcBounds.left) - x, y);
			}

			SelectObject(ps.hdc, hOldPen);
		}

		EndPaint(hwnd, &ps);
	}
	break;

case WM_VSCROLL:
	{
		int nScrollCode = (int)LOWORD(wParam);
		int nNewPos;
		int nPos = (short int)HIWORD(wParam);

		SCROLLINFO si = {sizeof(SCROLLINFO), 
			SIF_PAGE|SIF_POS|SIF_RANGE|SIF_TRACKPOS, 0, 0, 0, 0, 
			0};

		GetScrollInfo (fe->cfgbox, SB_VERT, &si);

		nNewPos = si.nPos;

		switch (nScrollCode)
		{
		case SB_BOTTOM:
			//Scrolls to the lower right 
			nNewPos = si.nMax;
			break;

		case SB_ENDSCROLL:
			// Ends scroll 
			break;

		case SB_LINEDOWN:
			// Scrolls one line down 
			nNewPos = nPos + 1;
			break;

		case SB_LINEUP:
			// Scrolls one line up 
			nNewPos = nPos - 1;
			break;

		case SB_PAGEDOWN:
			// Scrolls one page down
			nNewPos = nPos + DialogUnitToPixel_Y(fe->cfgbox, 15); // height of one row of configuration dialog
			break;

		case SB_PAGEUP:
			// Scrolls one page up 
			nNewPos = nPos - DialogUnitToPixel_Y(fe->cfgbox, 15); // height of one row of configuration dialog
			break;

		case SB_THUMBTRACK:
			// The user is dragging the scroll box. This message is sent repeatedly until the user releases the mouse button. The nPos parameter indicates the position that the scroll box has been dragged to. 
			nNewPos = si.nTrackPos;
			break;

		case SB_TOP:
			// Scrolls to the upper left 
			nNewPos = si.nMin;
			break;

		case SB_THUMBPOSITION:
			nNewPos = nPos + si.nMin; // Adding si.nMin is the workaround.
			break;
		}

		if (nNewPos < si.nMin)
			nNewPos = si.nMin;
		else if (nNewPos > si.nMax)
			nNewPos = si.nMax;

		if (si.nPos != nNewPos)
		{
			ScrollWindowEx(fe->cfgbox, 0, si.nPos - nNewPos, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_SCROLLCHILDREN);
			//UpdateWindow(fe->cfgbox);

			si.fMask = SIF_POS;
			si.nPos = nNewPos;
			SetScrollInfo (fe->cfgbox, SB_VERT, &si, TRUE);
		}
	}
	break;

case WM_HOTKEY:
	SHSendBackToFocusWindow(msg, wParam, lParam);
	break;

case WM_COMMAND:
	/*
	* OK and Cancel are special cases.
	*/
	if ((LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
		if (LOWORD(wParam) == IDOK) {
			char *err = NULL;
			
			HCURSOR hOld = SetCursor(LoadCursor(NULL, IDC_WAIT));	
			err = frontend_set_config(fe, fe->cfg_which, fe->cfg);
			SetCursor(hOld);

			if (err) {
				MessageBox(hwnd, err, "Validation error",
					MB_ICONERROR | MB_OK);
			} else {
				EndDialog(hwnd, IDOK);
			}
		} else {
			EndDialog(hwnd, IDCANCEL);
		}
		return 0;
	}

	/*
	* First find the control whose id this is.
	*/
	for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
		if (j->ctlid == LOWORD(wParam))
			break;
	}
	if (i->type == C_END)
		return 0;		       /* not our problem */

	if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) {
		char buffer[4096];
		TCHAR wBuffer[4096];
		GetDlgItemText(fe->cfgbox, j->ctlid, wBuffer, 4096);
		WideCharToMultiByte(CP_ACP, 0, wBuffer, -1, buffer, 4096, NULL, NULL);
		buffer[lenof(buffer)-1] = '\0';
		sfree(i->sval);
		i->sval = dupstr(buffer);
	} else if (i->type == C_BOOLEAN && 
		(HIWORD(wParam) == BN_CLICKED ||
		HIWORD(wParam) == BN_DBLCLK)) {
			i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
	} else if (i->type == C_CHOICES)
	{
		if (fe->isSmartphone && HIWORD(wParam) == LBN_SELCHANGE)
		{
			i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid, LB_GETCURSEL, 0, 0);
		}
		else if (!fe->isSmartphone && HIWORD(wParam) == CBN_SELCHANGE)
		{
			i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid, CB_GETCURSEL, 0, 0);
		}
	}

	return TRUE;
	}

	return FALSE;
}

static void about(frontend *fe)
{
	DialogBoxParam(fe->inst, MAKEINTRESOURCE(IDD_ABOUT), fe->hwnd, AboutDlgProc, fe);
}

static int get_config(frontend *fe, int which)
{
	fe->cfg_which = which;

	return DialogBoxParam(fe->inst,
		MAKEINTRESOURCE(IDD_CONFIG),
		fe->hwnd, ConfigDlgProc,
		(LPARAM) fe) == IDOK;
}

static void calculate_bitmap_position(frontend *fe, int x, int y)
{
	/* Pocket PC - center the game in the full screen window */
	int yMargin;
	RECT rcClient;

	GetClientRect(fe->hwnd, &rcClient);
	fe->bitmapPosition.left = (rcClient.right  - x) / 2;

	yMargin = rcClient.bottom - y;
	yMargin -= get_statusbar_height(fe);

	fe->bitmapPosition.top = yMargin / 2;

	fe->bitmapPosition.right  = fe->bitmapPosition.left + x;
	fe->bitmapPosition.bottom = fe->bitmapPosition.top  + y;
}

static void new_bitmap(frontend *fe, int x, int y)
{
	HDC hdc;

	if (fe->bitmap) DeleteObject(fe->bitmap);

	hdc = GetDC(fe->hwnd);
	fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
	calculate_bitmap_position(fe, x, y);
	ReleaseDC(fe->hwnd, hdc);
}

static void new_game_size(frontend *fe)
{
	int x, y;

	get_max_puzzle_size(fe, &x, &y);
	midend_size(fe->me, &x, &y, TRUE/*[CF] - NOTE - this was FALSE*/);
	fe->ymin = (fe->xmin * y) / x;

	check_window_size(fe, &x, &y);

	new_bitmap(fe, x, y);

	InvalidateRect(fe->hwnd, NULL, TRUE);
	midend_redraw(fe->me);
}

/*
* Given a proposed new window rect, work out the resulting
* difference in client size (from current), and use to try
* and resize the puzzle, returning (wx,wy) as the actual
* new window size.
*/

static void adjust_game_size(frontend *fe, RECT *proposed, int isedge,
							 int *wx_r, int *wy_r)
{
	RECT cr, wr;
	int nx, ny, xdiff, ydiff, wx, wy;

	/* Work out the current window sizing, and thus the
	* difference in size we're asking for. */
	GetClientRect(fe->hwnd, &cr);
	wr = cr;
	AdjustWindowRectEx(&wr, WS_CAPTION | WS_SYSMENU, TRUE, 0);

	xdiff = (proposed->right - proposed->left) - (wr.right - wr.left);
	ydiff = (proposed->bottom - proposed->top) - (wr.bottom - wr.top);

	if (isedge) {
		/* These next four lines work around the fact that midend_size
		* is happy to shrink _but not grow_ if you change one dimension
		* but not the other. */
		if (xdiff > 0 && ydiff == 0)
			ydiff = (xdiff * (wr.right - wr.left)) / (wr.bottom - wr.top);
		if (xdiff == 0 && ydiff > 0)
			xdiff = (ydiff * (wr.bottom - wr.top)) / (wr.right - wr.left);
	}

	if (check_window_resize(fe,
		(cr.right - cr.left) + xdiff,
		(cr.bottom - cr.top) + ydiff,
		&nx, &ny, &wx, &wy, TRUE)) {
			new_bitmap(fe, nx, ny);
			midend_force_redraw(fe->me);
	} else {
		/* reset size to current window size */
		wx = wr.right - wr.left;
		wy = wr.bottom - wr.top;
	}
	/* Re-fetch rectangle; size limits mean we might not have
	* taken it quite to the mouse drag positions. */
	GetClientRect(fe->hwnd, &cr);
	adjust_statusbar(fe, &cr);

	*wx_r = wx; *wy_r = wy;
}

static void new_game_type(frontend *fe)
{
	midend_new_game(fe->me);
	new_game_size(fe);
}

static int is_alt_pressed(void)
{
	BYTE keystate[256];
	int r = GetKeyboardState(keystate);
	if (!r)
		return FALSE;
	if (keystate[VK_MENU] & 0x80)
		return TRUE;
	if (keystate[VK_RMENU] & 0x80)
		return TRUE;
	return FALSE;
}

static void savefile_write(void *wctx, void *buf, int len)
{
	FILE *fp = (FILE *)wctx;
	fwrite(buf, 1, len, fp);
}

static int savefile_read(void *wctx, void *buf, int len)
{
	FILE *fp = (FILE *)wctx;
	int ret;

	ret = fread(buf, 1, len, fp);
	return (ret == len);
}

static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
								WPARAM wParam, LPARAM lParam)
{
	frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
	int cmd;

	static SHACTIVATEINFO sai = {0};

	switch (message) {
case WM_CREATE:
	memset(&sai, 0, sizeof(sai));
	sai.cbSize = sizeof(sai);
	break;
case WM_ACTIVATE:
	SHHandleWMActivate(hwnd, wParam, lParam, &sai, 0);
	break;
case WM_SETTINGCHANGE:
	SHHandleWMSettingChange(hwnd, wParam, lParam, &sai);
	break;
case WM_SIZE:
	// {CF] - TODO - remove this if statement hack
	if (fe != NULL && fe->hwnd != NULL)
		new_game_size(fe);
	break;

case WM_CLOSE:
	DestroyWindow(hwnd);
	return 0;
case WM_COMMAND:
	/* Numeric pad sends WM_COMMAND messages */
	if ((wParam >= IDM_KEYEMUL) && (wParam < IDM_KEYEMUL + 256))
	{
		midend_process_key(fe->me, 0, 0, wParam - IDM_KEYEMUL);
	}
	cmd = wParam & ~0xF;	       /* low 4 bits reserved to Windows */
	switch (cmd) {
case IDM_NEW:
	{
		HCURSOR hOld=	SetCursor(LoadCursor(NULL, IDC_WAIT));	
		if (!midend_process_key(fe->me, 0, 0, 'n'))
			PostQuitMessage(0); // [CF] - TODO - why is this here???
		SetCursor(hOld);
	}
	break;
case IDM_RESTART:
	midend_restart_game(fe->me);
	break;
case IDM_UNDO:
	if (!midend_process_key(fe->me, 0, 0, 'u'))
		PostQuitMessage(0);
	break;
case IDM_REDO:
	if (!midend_process_key(fe->me, 0, 0, '\x12'))
		PostQuitMessage(0);
	break;
case IDM_SOLVE:
	{
		HCURSOR hOld=	SetCursor(LoadCursor(NULL, IDC_WAIT));
		char *msg = midend_solve(fe->me);
		SetCursor(hOld);

		if (msg)
			MessageBox(hwnd, msg, "Unable to solve",
			MB_ICONERROR | MB_OK);
	}
	break;
case IDM_QUIT:
	if (!midend_process_key(fe->me, 0, 0, 'q'))
		PostQuitMessage(0);
	break;
case IDM_CONFIG:
	if (get_config(fe, CFG_SETTINGS))
	{
		RefreshSettingsMenu(fe);
	}
	break;
case IDM_SEED:
	if (get_config(fe, CFG_SEED))
		new_game_type(fe);
	break;
case IDM_DESC:
	if (get_config(fe, CFG_DESC))
		new_game_type(fe);
	break;
case IDM_PRINT:
	if (get_config(fe, CFG_PRINT))
		print(fe);
	break;

case IDM_ABOUT:
	about(fe);
	break;

default:
	if (fe != NULL)
	{
		int p = (LOWORD(wParam) - IDM_PRESETS) / 0x10;

		if (p >= 0 && p < fe->npresets) {
			HCURSOR hOld = SetCursor(LoadCursor(NULL, IDC_WAIT));	

			midend_set_params(fe->me, fe->presets[p]);

			RefreshSettingsMenu(fe);

			SetCursor(hOld);
		}
	}
	break;
	}
	break;

case WM_DESTROY:
	PostQuitMessage(0);
	return 0;
case WM_PAINT:
	{
		PAINTSTRUCT p;
		HDC hdc, hdc2;
		HBITMAP prevbm;
		RECT rcDest;

		hdc = BeginPaint(hwnd, &p);
		hdc2 = CreateCompatibleDC(hdc);
		prevbm = SelectObject(hdc2, fe->bitmap);

		FillRect(hdc, &(p.rcPaint), (HBRUSH) GetStockObject(WHITE_BRUSH));

		IntersectRect(&rcDest, &(fe->bitmapPosition), &(p.rcPaint));
		BitBlt(hdc,
			rcDest.left, rcDest.top,
			rcDest.right - rcDest.left,
			rcDest.bottom - rcDest.top,
			hdc2,
			rcDest.left - fe->bitmapPosition.left,
			rcDest.top - fe->bitmapPosition.top,
			SRCCOPY);
		SelectObject(hdc2, prevbm);
		DeleteDC(hdc2);
		EndPaint(hwnd, &p);
	}
	return 0;
case WM_KEYDOWN:
	{
		int key = -1;
		BYTE keystate[256];
		int r = GetKeyboardState(keystate);
		int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0;
		int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0;

		switch (wParam) {
case VK_RETURN:
	// #define VK_ACTION   VK_F23              // Sent with VK_RETURN when doing Action on PPC rockers
	key = shift | ctrl | CURSOR_SELECT;
	break;
case VK_LEFT:
	// [CF] - TODO - at present this lParam & 0x0100.... check is desktop specific
	// investigate if there is a windows ce equivalent..
	// bit 24
	//		Specifies whether the key is an extended key, such as the right-hand ALT
	//		and CTRL keys that appear on an enhanced 101- or 102-key keyboard. The
	//		value is 1 if it is an extended key; otherwise, it is 0. 
	//
	//	if (!(lParam & 0x01000000))
	//		key = MOD_NUM_KEYPAD | '4';
	//	else
	key = shift | ctrl | CURSOR_LEFT;
	break;
case VK_RIGHT:
	//	if (!(lParam & 0x01000000))
	//		key = MOD_NUM_KEYPAD | '6';
	//	else
	key = shift | ctrl | CURSOR_RIGHT;
	break;
case VK_UP:
	//	if (!(lParam & 0x01000000))
	//		key = MOD_NUM_KEYPAD | '8';
	//	else
	key = shift | ctrl | CURSOR_UP;
	break;
case VK_DOWN:
	//	if (!(lParam & 0x01000000))
	//		key = MOD_NUM_KEYPAD | '2';
	//	else
	key = shift | ctrl | CURSOR_DOWN;
	break;
	/*
	* Diagonal keys on the numeric keypad.
	*/
case VK_PRIOR:
	if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '9';
	break;
case VK_NEXT:
	if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '3';
	break;
case VK_HOME:
	if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '7';
	break;
case VK_END:
	if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '1';
	break;
case VK_INSERT:
	if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '0';
	break;
case VK_CLEAR:
	if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '5';
	break;
	/*
	* Numeric keypad keys with Num Lock on.
	*/
case VK_NUMPAD4: key = MOD_NUM_KEYPAD | '4'; break;
case VK_NUMPAD6: key = MOD_NUM_KEYPAD | '6'; break;
case VK_NUMPAD8: key = MOD_NUM_KEYPAD | '8'; break;
case VK_NUMPAD2: key = MOD_NUM_KEYPAD | '2'; break;
case VK_NUMPAD5: key = MOD_NUM_KEYPAD | '5'; break;
case VK_NUMPAD9: key = MOD_NUM_KEYPAD | '9'; break;
case VK_NUMPAD3: key = MOD_NUM_KEYPAD | '3'; break;
case VK_NUMPAD7: key = MOD_NUM_KEYPAD | '7'; break;
case VK_NUMPAD1: key = MOD_NUM_KEYPAD | '1'; break;
case VK_NUMPAD0: key = MOD_NUM_KEYPAD | '0'; break;
		}

		if (key != -1) {
			if (!midend_process_key(fe->me, 0, 0, key))
				PostQuitMessage(0);
		} else {
			MSG m;
			m.hwnd = hwnd;
			m.message = WM_KEYDOWN;
			m.wParam = wParam;
			m.lParam = lParam & 0xdfff;
			TranslateMessage(&m);
		}
	}
	break;
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
	{
		int button;

		/*
		* Shift-clicks count as middle-clicks, since otherwise
		* two-button Windows users won't have any kind of
		* middle click to use.
		*/
		if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
			button = MIDDLE_BUTTON;
		else if (message == WM_RBUTTONDOWN || is_alt_pressed())
			button = RIGHT_BUTTON;
		else if ((thegame.flags & REQUIRE_RBUTTON) == 0)
			button = LEFT_BUTTON;
		else
		{
			SHRGINFO shrgi;

			shrgi.cbSize     = sizeof(SHRGINFO);
			shrgi.hwndClient = hwnd;
			shrgi.ptDown.x   = (signed short)LOWORD(lParam);
			shrgi.ptDown.y   = (signed short)HIWORD(lParam);
			shrgi.dwFlags    = SHRG_RETURNCMD;

			if (GN_CONTEXTMENU == SHRecognizeGesture(&shrgi))
				button = RIGHT_BUTTON;
			else
				button = LEFT_BUTTON;
		}

		if (!midend_process_key(fe->me,
			(signed short)LOWORD(lParam) - fe->bitmapPosition.left,
			(signed short)HIWORD(lParam) - fe->bitmapPosition.top,
			button))
			PostQuitMessage(0);

		SetCapture(hwnd);
	}
	break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
	{
		int button;
		/*
		* Shift-clicks count as middle-clicks, since otherwise
		* two-button Windows users won't have any kind of
		* middle click to use.
		*/
		if (message == WM_MBUTTONUP || (wParam & MK_SHIFT))
			button = MIDDLE_RELEASE;
		else if (message == WM_RBUTTONUP || is_alt_pressed())
			button = RIGHT_RELEASE;
		else
			button = LEFT_RELEASE;

		if (!midend_process_key(fe->me,
			(signed short)LOWORD(lParam) - fe->bitmapPosition.left,
			(signed short)HIWORD(lParam) - fe->bitmapPosition.top,
			button))
			PostQuitMessage(0);

		ReleaseCapture();
	}
	break;
case WM_MOUSEMOVE:
	{
		int button;

		if (wParam & (MK_MBUTTON | MK_SHIFT))
			button = MIDDLE_DRAG;
		else if (wParam & MK_RBUTTON || is_alt_pressed())
			button = RIGHT_DRAG;
		else
			button = LEFT_DRAG;

		if (!midend_process_key(fe->me,
			(signed short)LOWORD(lParam) - fe->bitmapPosition.left,
			(signed short)HIWORD(lParam) - fe->bitmapPosition.top,
			button))
			PostQuitMessage(0);
	}
	break;
case WM_CHAR:
	if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
		PostQuitMessage(0);
	return 0;
case WM_TIMER:
	if (fe->timer) {
		DWORD now = GetTickCount();
		float elapsed = (float) (now - fe->timer_last_tickcount) * 0.001F;
		midend_timer(fe->me, elapsed);
		fe->timer_last_tickcount = now;
	}
	return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

static int FindPreviousInstance()
{
	/* Check if application is running. If it's running then focus on the window */
	HWND hOtherWnd = NULL;

	hOtherWnd = FindWindow (wGameName, wGameName);
	if (hOtherWnd)
	{
		SetForegroundWindow (hOtherWnd);
		return TRUE;
	}

	return FALSE;
}

int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
{
	MSG msg;
	char *error;
	char *pszState;

	MultiByteToWideChar (CP_ACP, 0, thegame.name, -1, wGameName, 256);
	if (FindPreviousInstance ())
		return 0;

	InitCommonControls();
	SHInitExtraControls();

	if (!prev) {
		WNDCLASS wndclass;

		wndclass.style = 0;
		wndclass.lpfnWndProc = WndProc;
		wndclass.cbClsExtra = 0;
		wndclass.cbWndExtra = 0;
		wndclass.hInstance = inst;
		wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(200));
		wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
		wndclass.hbrBackground = NULL;
		wndclass.lpszMenuName = NULL;
		wndclass.lpszClassName = wGameName;

		RegisterClass(&wndclass);
	}

	while (*cmdline && isspace((unsigned char)*cmdline))
		cmdline++;

	if (*cmdline)
		pszState = cmdline;
	else
		pszState = ReadGameParameters();

	if (!new_window(inst, pszState, &error)) 
	{
		char buf[128];
		sprintf(buf, "%.100s Error", thegame.name);
		MessageBox(NULL, error, buf, MB_OK|MB_ICONERROR);
		return 1;
	}

	while (GetMessage(&msg, NULL, 0, 0)) {
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
